/**
 * @file gvozdarev_2b.c
 * @author Gvozdarev Sergey, gr016 MIPT
 * MIT license
 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "mpi.h"


#define G_ELEM_NUM 1
#define G_FINISH   -1
#define G_MAIN_RANK 0

typedef float real_t;

void optim_distrib( int comm_size, int data_size, int * displs, int * counts)
{
    int to_all = data_size / comm_size;
    int mod = data_size % comm_size;

    int i;
    int curr = 0;
    for ( i = 0; i < mod; i++)
    {
        counts[ i] = to_all + 1;
        displs[ i] = curr;
        curr += counts[ i];

    }
    for ( i = mod; i < comm_size; i++)
    {
        counts[ i] = to_all;
        displs[ i] = curr;
        curr += counts[ i];

    }
}



int main( int argc, char ** argv)  
{
    double ttotal_begin, ttotal_end;
    double tbcast_begin, tbcast_end;
    double tred_begin,   tred_end; 
    double tcalc_begin,  tcalc_end;

	int comm_rank,comm_size;

    // check arg
    if ( argc != 1)
    {
        printf( "Nothing must be defined\n");
        return 1;
    }

	//// Init
    MPI_Init( &argc, &argv); 
    ttotal_begin = MPI_Wtime();

	MPI_Comm_rank( MPI_COMM_WORLD, &comm_rank );
    MPI_Comm_size( MPI_COMM_WORLD, &comm_size );
    
    if ( comm_size < 1)
    {
        printf("comm_size must be bigger than 0");
        MPI_Finalize();
        return 0;
    }

    int * buf;
    int * displs;
    int * counts;
    int N, chk_min, chk_max;
    real_t chk_aver;

    if ( comm_rank == 0) // main process
    {
        // read std

        scanf( "%d", &N);

        if ( N < 4)
        {
            printf ( "N < 4\n");
            return 1; 
        }

        N -= 3;
        
        int * glob_buf  = (int*)malloc( sizeof( int) * N);
       

        displs = (int*)malloc( sizeof( int) * comm_size);
        counts = (int*)malloc( sizeof( int) * comm_size);

        optim_distrib( comm_size, N, displs, counts);
                
        buf = (int*)malloc( sizeof( int) * counts[ comm_rank]);

        int i;
        for ( i = 0; i < N; i++)
        {
            scanf( "%d", glob_buf + i);
        }
        scanf( "%d", &chk_min);
        scanf( "%d", &chk_max);
        scanf( "%f", &chk_aver);
        
        // bcast  scattering
        tbcast_begin = MPI_Wtime();

        MPI_Bcast( &N,  1, MPI_INT, G_MAIN_RANK, MPI_COMM_WORLD);
        
        MPI_Scatterv( glob_buf, counts, displs, MPI_INT, buf, counts[ comm_rank], MPI_INT, G_MAIN_RANK, MPI_COMM_WORLD);
        free( glob_buf);
        tbcast_end   = MPI_Wtime();
    }
    else 
    { // workers
        //broadcasting
	  
        MPI_Bcast( &N,  1, MPI_INT, G_MAIN_RANK, MPI_COMM_WORLD);
       
        displs = (int*)malloc( sizeof( int) * comm_size);
        counts = (int*)malloc( sizeof( int) * comm_size);

        optim_distrib( comm_size, N, displs, counts); 

        buf = (int*)malloc( sizeof( int) * counts[ comm_rank]);
        
        MPI_Scatterv( NULL, counts, displs, MPI_INT, buf, counts[ comm_rank], MPI_INT, G_MAIN_RANK, MPI_COMM_WORLD);
    }
    // calculating

    tcalc_begin = MPI_Wtime();

    int i, min, max, sum;
    sum = min = max = buf[ 0];
    
    for ( i = 1; i < counts[ comm_rank]; i++)
    {
        min = ( buf[ i] < min) ? buf[ i] : min;
        max = ( buf[ i] > max) ? buf[ i] : max;
        sum += buf[ i];
    }

    int * res = (int*)malloc( sizeof( int) * 3);
    res[ 0] = min;
    res[ 1] = max;
    res[ 2] = sum;

    tcalc_end = MPI_Wtime();
    // gathering result
    tred_begin = MPI_Wtime();



    if ( comm_rank == G_MAIN_RANK)
    {
        int * glob_res = (int*)malloc( sizeof( int) * 3 * comm_size);
        MPI_Gather( res, 3, MPI_INT, glob_res, 3, MPI_INT, G_MAIN_RANK, MPI_COMM_WORLD);
 

        min = glob_res[ 0]; 
        max = glob_res[ 1];
        sum = glob_res[ 2];
        
        for ( i = 1; i < comm_size; i++)
        {
            min = ( glob_res[ 3*i+0] < min) ? glob_res[ 3*i+0] : min;
            max = ( glob_res[ 3*i+1] > max) ? glob_res[ 3*i+1] : max;
            sum +=  glob_res[ 3*i+2];
        }

        free( glob_res);

    
        ttotal_end = tred_end = MPI_Wtime();
        printf( "Result: %d %d %d %d",
                (int)( 1000000 * ( tbcast_end - tbcast_begin)),
                (int)( 1000000 * ( tcalc_end - tcalc_begin)),
                (int)( 1000000 * ( tred_end - tred_begin)),
                (int)( 1000000 * ( ttotal_end - ttotal_begin))
              );
        
        if ( chk_min == min && chk_max == max && ( chk_aver - (float)sum/N < 0.1))
        {
            printf( " OK\n");
        } 
        else
        {
            printf( " FAIL\n");
            printf( "Must be: %d\t%d\t%f\n", chk_min, chk_max, chk_aver);
            printf( "In real: %d\t%d\t%f\n", min, max, (float)sum/N);
       }
    }
    else
    {

        MPI_Gather( res, 3, MPI_INT, NULL, (int)NULL, NULL, G_MAIN_RANK, MPI_COMM_WORLD);
    }

    free( buf);
    free( displs);
    free( counts);
    free( res);

    MPI_Barrier( MPI_COMM_WORLD);
    MPI_Finalize();
    return 0;
} 
